/*
 * Ingenic IMP System Control module implement.
 *
 * Copyright (C) 2014 Ingenic Semiconductor Co.,Ltd
 * Author: Zoro <ykli@ingenic.cn>
 */

#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <linux/rtc.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

#include <sysutils/su_base.h>
#include <imp/imp_log.h>

#define TAG "base"
#define RTC_FILE "/dev/rtc0"

int SU_Base_GetModelNumber(SUModelNum *modelNum)
{
	char ifm[128] = "";
	int len = 0;
#define MODELNUMBER_FILE "/proc/cpuinfo"
	FILE *fd = fopen(MODELNUMBER_FILE, "r");
	if(fd == NULL) {
		IMP_LOG_ERR(TAG, "Get Model Number Error\n");
		return -1;
	}

	if(fgets(ifm, sizeof(ifm), fd) == NULL) {
		IMP_LOG_ERR(TAG, "fgets Model Number Error\n");
		fclose(fd);
		return -1;
	}

	fclose(fd);

	strcpy(modelNum->chr, strchr(ifm, ':') + 2);
	len = strlen(modelNum->chr);
	if(modelNum->chr[len-1] == '\n')
		modelNum->chr[len-1] = '\0';

	return 0;
}

int SU_Base_GetVersion(SUVersion *version)
{
	if (version == NULL)
		return -1;

	sprintf(version->chr, "SYSUTILS-%s", CONFIG_SDK_VERSION);
	return 0;
}

static int regrw(uint32_t addr, uint32_t *value, int is_write)
{
	void *map_base, *virt_addr;
	off_t target;
	unsigned page_size, mapped_size, offset_in_page;
	int fd, width = 32, ret = 0;

	target = addr;
	fd = open("/dev/mem", is_write ? (O_RDWR | O_SYNC) : (O_RDONLY | O_SYNC));
	if (fd < 0) {
	    IMP_LOG_ERR(TAG, "open /dev/mem failed\n");
	    return -1;
	}

	mapped_size = page_size = getpagesize();
	offset_in_page = (unsigned)target & (page_size - 1);
	if (offset_in_page + width > page_size) {
	    /* This access spans pages.
	     * Must map two pages to make it possible: */
	    mapped_size *= 2;
	}
	map_base = mmap(NULL,
	                mapped_size,
	                is_write ? (PROT_READ | PROT_WRITE) : PROT_READ,
	                MAP_SHARED,
	                fd,
	                target & ~(off_t)(page_size - 1));
	if (map_base == MAP_FAILED) {
	    IMP_LOG_ERR(TAG, "mmap failed\n");
	    ret = -1;
	    goto close_fd;
	}

	virt_addr = (char*)map_base + offset_in_page;

	if (!is_write)
	    *value = *(volatile uint32_t*)virt_addr;
	else
	    *(volatile uint32_t*)virt_addr = *value;

	if (munmap(map_base, mapped_size) == -1)
	    IMP_LOG_ERR(TAG, "munmap failed\n");

close_fd:
	close(fd);

	return ret;
}

int SU_Base_GetDevID(SUDevID *devID)
{
/*  int i;
    unsigned int temp;
    FILE * fp;
    char buffer[1024];
    char *read_register[] = {"devmem 0x13540200", "devmem 0x13540204", "devmem 0x13540208"};

    memset(buffer, sizeof(buffer), 0);

    for (i = 0 ; i < 3 ; i++) {
        fp=popen(read_register[i],"r");
        fgets(buffer,sizeof(buffer),fp);
        temp = strtoul(buffer, NULL, 16);
        sprintf(buffer, "%x", temp);

        temp = buffer[0];
        buffer[0] = buffer[6];
        buffer[6] = temp;

        temp = buffer[1];
        buffer[1] = buffer[7];
        buffer[7] = temp;

        temp = buffer[2];
        buffer[2] = buffer[4];
        buffer[4] = temp;

        temp = buffer[3];
        buffer[3] = buffer[5];
        buffer[5] = temp;

        sprintf(devID->chr, "%s%s", devID->chr, buffer);
        pclose(fp);
    }
*/
    int i, j, k, len;
    unsigned int temp;
    uint32_t read_value;
    uint32_t regAddr[] = {0x13540200, 0x13540204, 0x13540208};
    char buffer[1024];

    memset(buffer, 0, sizeof(buffer));

    for (i = 0 ; i < 3 ; i++) {
        if (regrw(regAddr[i], &read_value, 0) < 0) {
            IMP_LOG_ERR(TAG, "%s(): failed\n");
            return -1;
        }
        sprintf(buffer, "%08x", read_value);

        temp = buffer[0];
        buffer[0] = buffer[6];
        buffer[6] = temp;

        temp = buffer[1];
        buffer[1] = buffer[7];
        buffer[7] = temp;

        temp = buffer[2];
        buffer[2] = buffer[4];
        buffer[4] = temp;

        temp = buffer[3];
        buffer[3] = buffer[5];
        buffer[5] = temp;

        sprintf(devID->chr, "%s%s", devID->chr, buffer);
    }

	return 0;
}

int SU_Base_Shutdown(void)
{
	sync();
	kill(1, SIGUSR2);

	return 0;
}

int SU_Base_Reboot(void)
{
	sync();
	kill(1, SIGTERM);

	return 0;
}

int SU_Base_Suspend()
{
	int ret = 0;
	int state_fd = -1;
	const char* state_patch = "/sys/power/state";
	static pthread_mutex_t singleton_mutex = PTHREAD_MUTEX_INITIALIZER;

	/* lock */
	pthread_mutex_lock(&singleton_mutex);

	/* suspend */
	state_fd = open(state_patch, O_RDWR);
	if (state_fd < 0) {
		IMP_LOG_ERR(TAG, "when open %s\n", state_patch);
		ret = -1;
		goto error;
	}

	ret = write(state_fd, "mem", 3);
	if (ret != 3) {
		IMP_LOG_ERR(TAG, "Error when suspend\n");
		ret = -1;
	} else {
		ret = 0;
	}

	close(state_fd);
error:
	/* unlock */
	pthread_mutex_unlock(&singleton_mutex);

	return ret;
}


static int is_valid_sutime(SUTime *suTime)
{
	if (suTime == NULL)
		return 0;
	/* TODO other case here */
	return 1;
}

int SU_Base_GetTime(SUTime *suTime)
{
	if (!is_valid_sutime(suTime))
		return -1;


	int fd = open(RTC_FILE, O_RDONLY);
	if (fd == -1) {
		IMP_LOG_ERR(TAG,"open rtc node fail: %s\n", strerror(errno), __func__, __LINE__);
		return -1;
	}

	int ret = 0;
	struct rtc_time rtc_tm;

	ret = ioctl(fd, RTC_RD_TIME, &rtc_tm);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IOCTL RTC_ALM_READ fail \n");
		close(fd);
		return -1;
	}

	suTime->sec = rtc_tm.tm_sec;
	suTime->min = rtc_tm.tm_min;
	suTime->hour = rtc_tm.tm_hour;
	suTime->mday = rtc_tm.tm_mday;
	suTime->mon = rtc_tm.tm_mon + 1;
	suTime->year = rtc_tm.tm_year + 1900;

	close(fd);

	return ret;
}

int SU_Base_SetTime(SUTime *suTime)
{
	if (!is_valid_sutime(suTime))
		return -1;

	int fd = open(RTC_FILE, O_RDONLY);
	if (fd == -1) {
		IMP_LOG_ERR(TAG,"open rtc node fail: %s\n", strerror(errno), __func__, __LINE__);
		return -1;
	}

	int ret = 0;
	struct rtc_time rtc_tm;

	rtc_tm.tm_sec  = suTime->sec;
	rtc_tm.tm_min  = suTime->min;
	rtc_tm.tm_hour = suTime->hour;
	rtc_tm.tm_mday = suTime->mday;
	rtc_tm.tm_mon  = suTime->mon - 1;
	rtc_tm.tm_year = suTime->year - 1900;
	rtc_tm.tm_isdst = 0;

	ret = ioctl(fd, RTC_SET_TIME, &rtc_tm);
	if (ret == -1)
		IMP_LOG_ERR(TAG, "IOCTL RTC_ALM_READ fail \n");

	close(fd);

	return ret;
}

int SU_Base_SUTime2Raw(SUTime *suTime, uint32_t *rawTime)
{
	if (suTime == NULL) {
		IMP_LOG_ERR(TAG, "*suTime is invalid\n");
		return -1;
	}

	if (rawTime == NULL) {
		IMP_LOG_ERR(TAG, "*rawTime is invalid\n");
		return -1;
	}

	unsigned int year0 = suTime->year;
	unsigned int mon0 = suTime->mon;
	unsigned int day = suTime->mday;
	unsigned int hour = suTime->hour;
	unsigned int min = suTime->min;
	unsigned int sec = suTime->sec;
	unsigned int mon = mon0, year = year0;

	/* 1..12 -> 11,12,1..10 */
	if (0 >= (int) (mon -= 2)) {
		mon += 12;	/* Puts Feb last since it has leap day */
		year -= 1;
	}

	*rawTime = ((((unsigned long)
		  (year/4 - year/100 + year/400 + 367*mon/12 + day) +
		  year*365 - 719499
	    )*24 + hour /* now have hours */
	  )*60 + min /* now have minutes */
	)*60 + sec; /* finally seconds */

	return 0;
}

static inline bool is_leap_year(unsigned int year)
{
	return (!(year % 4) && (year % 100)) || !(year % 400);
}

static const unsigned char rtc_days_in_month[] = {
	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

static int rtc_month_days(unsigned int month, unsigned int year)
{
	return rtc_days_in_month[month] + (is_leap_year(year) && month == 1);
}

int SU_Base_Raw2SUTime(uint32_t *rawTime, SUTime *suTime)
{
	if (suTime == NULL) {
		IMP_LOG_ERR(TAG, "*suTime is invalid\n");
		return -1;
	}

	if (rawTime == NULL) {
		IMP_LOG_ERR(TAG, "*rawTime is invalid\n");
		return -1;
	}

	unsigned int time = *rawTime;
	unsigned int month, year;
	int days;

	days = time / 86400;
	time -= (unsigned int) days * 86400;

	year = 1970 + days / 365;
#define LEAPS_THRU_END_OF(y) ((y)/4 - (y)/100 + (y)/400)
	days -= (year - 1970) * 365
		+ LEAPS_THRU_END_OF(year - 1)
		- LEAPS_THRU_END_OF(1970 - 1);
	if (days < 0) {
		year -= 1;
		days += 365 + is_leap_year(year);
	}
	suTime->year = year - 1900;

	for (month = 0; month < 11; month++) {
		int newdays;

		newdays = days - rtc_month_days(month, year);
		if (newdays < 0)
			break;
		days = newdays;
	}
	suTime->mon = month;
	suTime->mday = days + 1;

	suTime->hour = time / 3600;
	time -= suTime->hour * 3600;
	suTime->min = time / 60;
	suTime->sec = time - suTime->min * 60;

	return 0;
}

int SU_Base_SetAlarm(SUTime *suTime)
{
	int ret = 0;
	struct rtc_time rtc_tm;

	if (!is_valid_sutime(suTime))
		return -1;

	int fd = open(RTC_FILE, O_RDONLY);
	if (fd == -1) {
		IMP_LOG_ERR(TAG,"open rtc node fail: %s\n", strerror(errno), __func__, __LINE__);
		return -1;
	}

	rtc_tm.tm_sec  = suTime->sec;
	rtc_tm.tm_min  = suTime->min;
	rtc_tm.tm_hour = suTime->hour;
	rtc_tm.tm_mday = suTime->mday;
	rtc_tm.tm_mon  = suTime->mon - 1;
	rtc_tm.tm_year = suTime->year - 1900;
	rtc_tm.tm_isdst = 0;

	ret = ioctl(fd, RTC_ALM_SET,&rtc_tm);
	if (ret == -1)
		IMP_LOG_ERR(TAG,"IOCTL RTC_ALM_READ fail \n");

	close(fd);

	return ret;
}

int SU_Base_GetAlarm(SUTime *suTime)
{
	int ret = 0;
	struct rtc_time rtc_tm;

	int fd = open(RTC_FILE,O_RDONLY);
	if (fd == -1) {
		IMP_LOG_ERR(TAG,"open rtc node fail: %s\n", strerror(errno), __func__, __LINE__);
		return -1;
	}

	ret = ioctl(fd, RTC_ALM_READ, &rtc_tm);
	if (ret == -1) {
		IMP_LOG_ERR(TAG,"IOCTL RTC_ALM_READ fail \n");
		close(fd);
		return ret;
	}

	suTime->sec  = rtc_tm.tm_sec;
	suTime->min  = rtc_tm.tm_min;
	suTime->hour = rtc_tm.tm_hour;
	suTime->mday = rtc_tm.tm_mday;
	suTime->mon  = rtc_tm.tm_mon + 1;
	suTime->year = rtc_tm.tm_year + 1900;

	close(fd);

	return 0;
}

static int time_compare()
{
	int ret = 0;
	SUTime time_now;
	SUTime time_alarm;

	ret = SU_Base_GetTime(&time_now);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "SU_Base_GetTime() error\n");
		return -1;
	}

	ret = SU_Base_GetAlarm(&time_alarm);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "SU_Base_GetTime() error\n");
		return -1;
	}

	if (time_now.year > time_alarm.year)
		return -1;
	else if (time_now.year < time_alarm.year)
		return 0;

	if (time_now.mon > time_alarm.mon)
		return -1;
	else if (time_now.mon < time_alarm.mon)
		return 0;

	if (time_now.mday > time_alarm.mday)
		return -1;
	else if (time_now.mday < time_alarm.mday)
		return 0;

	if (time_now.hour > time_alarm.hour)
		return -1;
	else if (time_now.hour < time_alarm.hour)
		return 0;

	if (time_now.min > time_alarm.min)
		return -1;
	else if (time_now.min < time_alarm.min)
		return 0;

	if (time_now.sec > time_alarm.sec)
		return -1;
	else if (time_now.sec < time_alarm.sec)
		return 0;

	return -1;
}

int SU_Base_EnableAlarm(void)
{
	int ret;

	if (time_compare() == -1){
		IMP_LOG_ERR(TAG,"Alarm Time Invalid ,forbid to enable alram\n");
		return -1;
	}

	int fd = open(RTC_FILE, O_RDONLY);
	if (fd == -1) {
		IMP_LOG_ERR(TAG,"open rtc node fail: %s\n", strerror(errno), __func__, __LINE__);
		return -1;
	}

	ret = ioctl(fd, RTC_AIE_ON, 0);
	if (ret == -1)
		IMP_LOG_ERR(TAG,"IOCTL RTC_AIE_ON fail \n");

	close(fd);

	return ret;
}

int SU_Base_DisableAlarm(void)
{
	int ret = 0;

	int fd = open(RTC_FILE, O_RDONLY);
	if (fd == -1) {
		IMP_LOG_ERR(TAG,"open rtc node fail: %s\n", strerror(errno), __func__, __LINE__);
		return -1;
	}

	ret = ioctl(fd, RTC_AIE_OFF, 0);
	if (ret == -1)
		IMP_LOG_ERR(TAG,"IOCTL RTC_AIE_OFF fail \n");

	close(fd);

	return ret;
}

int SU_Base_PollingAlarm(uint32_t timeoutMsec)
{
	int ret = 0;
	fd_set readfd;
	struct timeval tv;

	int fd = open(RTC_FILE, O_RDONLY);
	if (fd == -1) {
		IMP_LOG_ERR(TAG,"open rtc node fail: %s\n", strerror(errno), __func__, __LINE__);
		return -1;
	}

	FD_ZERO(&readfd);
	FD_SET(fd,&readfd);

	//set timeout
	tv.tv_sec = timeoutMsec / 1000;
	tv.tv_usec = (timeoutMsec % 1000)*1000;

	ret = select(fd+1, &readfd, NULL, NULL, &tv);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "select error: %s\n", strerror(errno));
	} else if (ret == 0) {
		IMP_LOG_ERR(TAG, "select timeout: %s\n", strerror(errno));
		ret = -1;
	}

	close(fd);

	return 0;
}
